package main

import (
	"bufio"
	"context"
	"fmt"
	"io/fs"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"syscall"
	"time"

	"github.com/fatih/color"
)

const (
	VERSION      = "1.0.0"
	TARGET_FILE  = ".txt"
	statsLine    = 15 // 统计信息显示行号
	maxNameWidth = 30 // 文件名最大显示宽度
	maxLineWidth = 40 // 内容最大显示宽度
)

// 搜索结果
type SearchResult struct {
	File    string // 文件路径
	Line    int    // 行号
	Content string // 匹配行内容
}

// 进度统计
type Progress struct {
	FilesScanned int64 // 已扫描文件数
	FilesFound   int64 // 包含匹配的文件数
	LinesScanned int64 // 已扫描行数
	MatchesFound int64 // 总匹配次数
	mutex        sync.Mutex
}

func (p *Progress) Add(filesScanned, filesFound, linesScanned, linesFound int64) {
	//p.mutex.Lock()
	//defer p.mutex.Unlock()
	//p.FilesScanned += filesScanned
	//p.FilesFound += filesFound
	//p.LinesScanned += linesScanned
	//p.MatchesFound += linesFound

	atomic.AddInt64(&p.FilesScanned, filesScanned)
	atomic.AddInt64(&p.FilesFound, filesFound)
	atomic.AddInt64(&p.LinesScanned, linesScanned)
	atomic.AddInt64(&p.MatchesFound, linesFound)

	//	TODO 刷新
}

// 并发搜索入口
func concurrentSearch(ctx context.Context, directory, keyword string, progress *Progress, results chan<- SearchResult) error {
	var wg sync.WaitGroup
	filepaths := make(chan string, 100)
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer close(filepaths)
		walkDir(ctx, directory, filepaths)
	}()
	workers := runtime.NumCPU() * 2
	for i := 0; i < workers; i++ {
		wg.Add(1)
		go worker(ctx, &wg, filepaths, keyword, progress, results)
	}
	wg.Wait()
	return nil
}

// 目录遍历
func walkDir(ctx context.Context, dir string, filePaths chan<- string) error {
	err := filepath.Walk(dir, func(path string, info fs.FileInfo, err error) error {
		if ctx.Err() != nil {
			return ctx.Err()
		}
		if err != nil {
			fmt.Printf("访问 %q 失败: %v\n", path, err)
			return filepath.SkipDir
		}
		if !info.IsDir() {
			filePaths <- path
		}
		return nil
	})
	if err != nil {
		fmt.Println(err)
		return err
	}
	return nil
}

// 文件处理工作协程
func worker(ctx context.Context, wg *sync.WaitGroup, filePaths <-chan string, keyword string, progress *Progress, results chan<- SearchResult) {
	defer wg.Done()
	for file := range filePaths {
		if ctx.Err() != nil {
			return
		}
		err := processFile(ctx, file, keyword, progress, results)
		if err != nil {
			fmt.Println("处理文件失败：", err)
		}
	}
}

// 处理单个文件
func processFile(ctx context.Context, filePath, keyword string, progress *Progress, results chan<- SearchResult) error {
	if ctx.Err() != nil {
		return ctx.Err()
	}
	file, err := os.Open(filePath)
	if err != nil {
		fmt.Println("文件打开失败", err)
		return err
	}
	defer file.Close()
	scanner := bufio.NewScanner(file)
	lineNum := 1
	matchLine := 0
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Contains(line, keyword) {
			results <- SearchResult{File: filePath, Line: lineNum, Content: line}
			matchLine++
		}
		lineNum++
	}
	progress.Add(1, int64(min(1, matchLine)), int64(lineNum), int64(matchLine))
	return nil
}

func min(a, b int) int {
	if a > b {
		return b
	}
	return a
}

func signalProgress(cancel context.CancelFunc, wg *sync.WaitGroup, timeout int) {
	sigchan := make(chan os.Signal, 1)
	signal.Notify(sigchan, os.Interrupt, syscall.SIGTERM)

	select {
	case sig := <-sigchan:
		fmt.Println("接受到信号：", sig)
	case <-time.After(time.Duration(timeout) * time.Second):
		fmt.Println("搜索超时！")
	}
	cancel()
}

func displayResults(ctx context.Context, progress *Progress, key string, results <-chan SearchResult) {
	// 初始化显示
	fmt.Print("\033[?25l")       // 隐藏光标
	defer fmt.Print("\033[?25h") // 退出时恢复光标

	// 结果计数器
	resultCount := 0

	for {
		select {
		case <-ctx.Done():
			return
		case result, ok := <-results:
			if !ok {
				return // 结果通道已关闭
			}

			// 格式化显示结果
			displaySingleResult(result, resultCount, key)
			resultCount++

			// 更新统计信息
			updateStats(progress)
		}
	}
}

// 显示单个结果
func displaySingleResult(result SearchResult, count int, keyword string) {
	// 格式化文件名
	file := result.File
	if len(file) > maxNameWidth {
		file = "..." + file[len(file)-maxNameWidth+3:]
	}

	// 格式化内容
	content := result.Content
	if len(content) > maxLineWidth {
		content = content[:maxLineWidth-3] + "..."
	}

	// 高亮关键字
	content = highlightKeyword(content, keyword)

	// 打印结果（追加到现有内容下方）
	fmt.Printf("%s%-30s %s%-5d %s%s\n",
		color.CyanString(""),
		file,
		color.YellowString(""),
		result.Line,
		color.WhiteString(""),
		content,
	)
}

// 更新统计信息
func updateStats(progress *Progress) {
	// 保存当前光标位置
	fmt.Print("\033[s")

	// 移动到统计信息行
	fmt.Printf("\033[%d;1H", statsLine)

	// 清空统计行
	fmt.Print("\033[2K")

	// 获取当前统计值
	filesScanned := atomic.LoadInt64(&progress.FilesScanned)
	filesFound := atomic.LoadInt64(&progress.FilesFound)
	linesScanned := atomic.LoadInt64(&progress.LinesScanned)
	matchesFound := atomic.LoadInt64(&progress.MatchesFound)

	// 打印统计信息
	fmt.Printf("%s扫描文件: %-6d %s匹配文件: %-4d %s扫描行数: %-8d %s匹配项: %d",
		color.BlueString(""),
		filesScanned,
		color.GreenString(""),
		filesFound,
		color.YellowString(""),
		linesScanned,
		color.RedString(""),
		matchesFound,
	)

	// 恢复光标位置
	fmt.Print("\033[u")
}

// 高亮显示关键字
func highlightKeyword(line, keyword string) string {
	start := strings.Index(line, keyword)
	if start == -1 {
		return line
	}

	end := start + len(keyword)
	return line[:start] + color.RedString(line[start:end]) + line[end:]
}

func clearScreen() {
	fmt.Print("\033[2J")
	fmt.Print("\033[H")
}

func printHeader(directory, keyword string) {
	color.Cyan("Go并发文件搜索工具 v%s", VERSION)
	fmt.Println()
	color.Cyan("搜索目录: %s", directory)
	color.Cyan("搜索关键字: %s", keyword)
	fmt.Println()
	fmt.Println(strings.Repeat("=", 80))
}
func printHelp() {
	fmt.Println(color.CyanString("Go并发文件搜索工具 v%s", VERSION))
	fmt.Println("用法: search <目录> <关键字>")
	fmt.Println("示例: gosearch ./project 'import'")
}

func main() {
	println(9999999)
	if len(os.Args) < 4 {
		printHelp()
		return
	}
	directory := os.Args[1]
	keyword := os.Args[2]
	timeout, err := strconv.Atoi(os.Args[3])
	if err != nil {
		printHelp()
		return
	}
	fmt.Printf("🔍 开始在目录 %s 中搜索关键字: %s\n", directory, keyword)
	fmt.Println("按 Ctrl+C 取消搜索...")
	fmt.Printf("Searching dir %s with keyword: %s\n", directory, keyword)
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go signalProgress(cancel, &sync.WaitGroup{}, timeout)
	p := Progress{}
	results := make(chan SearchResult, 100)
	var displayWg sync.WaitGroup
	displayWg.Add(1)
	go func() {
		defer displayWg.Done()
		displayResults(ctx, &p, keyword, results)
	}()
	start := time.Now()
	err = concurrentSearch(ctx, directory, keyword, &p, results)
	elapsed := time.Since(start)
	close(results)
	printFinalStats(&p, elapsed, err)
}
func printFinalStats(progress *Progress, elapsed time.Duration, err error) {
	fmt.Println("\n" + strings.Repeat("=", 80))

	if err != nil {
		color.Red("搜索被取消: %v", err)
	} else {
		color.Green("✅ 搜索完成!")
	}

	fmt.Printf("⏱  耗时: %.2f 秒\n", elapsed.Seconds())
	fmt.Printf("📂 扫描文件数: %d\n", progress.FilesScanned)
	fmt.Printf("📊 扫描行数: %d\n", progress.LinesScanned)
	fmt.Printf("✅ 匹配文件数: %d\n", progress.FilesFound)
	fmt.Printf("🔤 总匹配数: %d\n", progress.MatchesFound)
	fmt.Println(strings.Repeat("=", 80))
}
